(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
    typeof define === 'function' && define.amd ? define(['exports'], factory) :
      (global = global || self, factory(global.VueTemplateCompiler = {}));
}(this, function (exports) {
  'use strict';

  var Vue // late bind
  var version
  var map = Object.create(null)
  if (typeof window !== 'undefined') {
    window.__VUE_HOT_MAP__ = map
  }
  var installed = false
  var isBrowserify = false
  var initHookName = 'beforeCreate'

  exports.install = function (vue, browserify) {
    if (installed) { return }
    installed = true

    Vue = vue.__esModule ? vue.default : vue
    version = Vue.version.split('.').map(Number)
    isBrowserify = browserify

    // compat with < 2.0.0-alpha.7
    if (Vue.config._lifecycleHooks.indexOf('init') > -1) {
      initHookName = 'init'
    }

    exports.compatible = version[0] >= 2
    if (!exports.compatible) {
      console.warn(
        '[HMR] You are using a version of vue-hot-reload-api that is ' +
        'only compatible with Vue.js core ^2.0.0.'
      )
      return
    }
  }

  /**
   * Create a record for a hot module, which keeps track of its constructor
   * and instances
   *
   * @param {String} id
   * @param {Object} options
   */

  exports.createRecord = function (id, options) {
    if (map[id]) { return }

    var Ctor = null
    if (typeof options === 'function') {
      Ctor = options
      options = Ctor.options
    }
    makeOptionsHot(id, options)
    map[id] = {
      Ctor: Ctor,
      options: options,
      instances: []
    }
  }

  /**
   * Check if module is recorded
   *
   * @param {String} id
   */

  exports.isRecorded = function (id) {
    return typeof map[id] !== 'undefined'
  }

  /**
   * Make a Component options object hot.
   *
   * @param {String} id
   * @param {Object} options
   */

  function makeOptionsHot(id, options) {
    if (options.functional) {
      var render = options.render
      options.render = function (h, ctx) {
        var instances = map[id].instances
        if (ctx && instances.indexOf(ctx.parent) < 0) {
          instances.push(ctx.parent)
        }
        return render(h, ctx)
      }
    } else {
      injectHook(options, initHookName, function () {
        var record = map[id]
        if (!record.Ctor) {
          record.Ctor = this.constructor
        }
        record.instances.push(this)
      })
      injectHook(options, 'beforeDestroy', function () {
        var instances = map[id].instances
        instances.splice(instances.indexOf(this), 1)
      })
    }
  }

  /**
   * Inject a hook to a hot reloadable component so that
   * we can keep track of it.
   *
   * @param {Object} options
   * @param {String} name
   * @param {Function} hook
   */

  function injectHook(options, name, hook) {
    var existing = options[name]
    options[name] = existing
      ? Array.isArray(existing) ? existing.concat(hook) : [existing, hook]
      : [hook]
  }

  function tryWrap(fn) {
    return function (id, arg) {
      try {
        fn(id, arg)
      } catch (e) {
        console.error(e)
        console.warn(
          'Something went wrong during Vue component hot-reload. Full reload required.'
        )
      }
    }
  }

  function updateOptions(oldOptions, newOptions) {
    for (var key in oldOptions) {
      if (!(key in newOptions)) {
        delete oldOptions[key]
      }
    }
    for (var key$1 in newOptions) {
      oldOptions[key$1] = newOptions[key$1]
    }
  }

  exports.rerender = tryWrap(function (id, options) {
    var record = map[id]
    if (!options) {
      record.instances.slice().forEach(function (instance) {
        instance.$forceUpdate()
      })
      return
    }
    if (typeof options === 'function') {
      options = options.options
    }
    if (record.Ctor) {
      record.Ctor.options.render = options.render
      record.Ctor.options.staticRenderFns = options.staticRenderFns
      record.instances.slice().forEach(function (instance) {
        instance.$options.render = options.render
        instance.$options.staticRenderFns = options.staticRenderFns
        // reset static trees
        // pre 2.5, all static trees are cached together on the instance
        if (instance._staticTrees) {
          instance._staticTrees = []
        }
        // 2.5.0
        if (Array.isArray(record.Ctor.options.cached)) {
          record.Ctor.options.cached = []
        }
        // 2.5.3
        if (Array.isArray(instance.$options.cached)) {
          instance.$options.cached = []
        }

        // post 2.5.4: v-once trees are cached on instance._staticTrees.
        // Pure static trees are cached on the staticRenderFns array
        // (both already reset above)

        // 2.6: temporarily mark rendered scoped slots as unstable so that
        // child components can be forced to update
        var restore = patchScopedSlots(instance)
        instance.$forceUpdate()
        instance.$nextTick(restore)
      })
    } else {
      // functional or no instance created yet
      record.options.render = options.render
      record.options.staticRenderFns = options.staticRenderFns

      // handle functional component re-render
      if (record.options.functional) {
        // rerender with full options
        if (Object.keys(options).length > 2) {
          updateOptions(record.options, options)
        } else {
          // template-only rerender.
          // need to inject the style injection code for CSS modules
          // to work properly.
          var injectStyles = record.options._injectStyles
          if (injectStyles) {
            var render = options.render
            record.options.render = function (h, ctx) {
              injectStyles.call(ctx)
              return render(h, ctx)
            }
          }
        }
        record.options._Ctor = null
        // 2.5.3
        if (Array.isArray(record.options.cached)) {
          record.options.cached = []
        }
        record.instances.slice().forEach(function (instance) {
          instance.$forceUpdate()
        })
      }
    }
  })

  exports.reload = tryWrap(function (id, options) {
    var record = map[id]
    if (options) {
      if (typeof options === 'function') {
        options = options.options
      }
      makeOptionsHot(id, options)
      if (record.Ctor) {
        if (version[1] < 2) {
          // preserve pre 2.2 behavior for global mixin handling
          record.Ctor.extendOptions = options
        }
        var newCtor = record.Ctor.super.extend(options)
        // prevent record.options._Ctor from being overwritten accidentally
        newCtor.options._Ctor = record.options._Ctor
        record.Ctor.options = newCtor.options
        record.Ctor.cid = newCtor.cid
        record.Ctor.prototype = newCtor.prototype
        if (newCtor.release) {
          // temporary global mixin strategy used in < 2.0.0-alpha.6
          newCtor.release()
        }
      } else {
        updateOptions(record.options, options)
      }
    }
    record.instances.slice().forEach(function (instance) {
      if (instance.$vnode && instance.$vnode.context) {
        instance.$vnode.context.$forceUpdate()
      } else {
        console.warn(
          'Root or manually mounted instance modified. Full reload required.'
        )
      }
    })
  })

  // 2.6 optimizes template-compiled scoped slots and skips updates if child
  // only uses scoped slots. We need to patch the scoped slots resolving helper
  // to temporarily mark all scoped slots as unstable in order to force child
  // updates.
  function patchScopedSlots(instance) {
    if (!instance._u) { return }
    // https://github.com/vuejs/vue/blob/dev/src/core/instance/render-helpers/resolve-scoped-slots.js
    var original = instance._u
    instance._u = function (slots) {
      try {
        // 2.6.4 ~ 2.6.6
        return original(slots, true)
      } catch (e) {
        // 2.5 / >= 2.6.7
        return original(slots, null, true)
      }
    }
    return function () {
      instance._u = original
    }
  }
}));
